import sys
import os
from time import sleep
from signal import SIGINT
from subprocess import Popen, PIPE
from tempfile import mkstemp
import shutil
import cloudcontrol._connections.remote

_sshd_loopback_servers = {}
"""keeps a mapping of port-->loopback_server objects"""

def ensure_sshd_loopback(port):
    """ensures that a loopback server is running on the given port"""
    if port not in _sshd_loopback_servers:
        _sshd_loopback_servers[port] = _SSHDLoopbackServer(port)
    if not _sshd_loopback_servers[port].is_running:
        sys.stderr.write("starting sshd on port %s ... " % port)
        _sshd_loopback_servers[port].start()

def stop_sshd_loopback(port):
    """ensures that the loopback server on the given port is stopped"""
    if port not in _sshd_loopback_servers:
        raise StandardError("no loopback on port %s exists" % port)
    else:
        if _sshd_loopback_servers[port].is_running:
            sys.stderr.write("stopping sshd on port %s\n" % port)
            _sshd_loopback_servers[port].stop()

_KEYCACHE_DIR = os.path.join(os.path.dirname(__file__), ".sshloopback_keys")
"""a directory that will hold cached keys generated with ssh-keygen (saves time when running unittests)"""

_CACHED_HOSTKEY = os.path.join(_KEYCACHE_DIR, "host_key")
_CACHED_HOSTKEY_PUB = "%s.pub" % _CACHED_HOSTKEY
_CACHED_CLIENTKEY = os.path.join(_KEYCACHE_DIR, "client_key")
_CACHED_CLIENTKEY_PUB = "%s.pub" % _CACHED_CLIENTKEY


class _SSHDLoopbackServer(object):
    """helper class for running a loopback ssh server"""
    
    def __init__(self, port):
        
        self.__port = port
        
        # set up all temp files
        # self.__client_key_filepath = mkstemp(".cloudcontrol.unittest.id_dsa")[1]
        self.__client_key_filepath = os.path.join(os.path.expanduser("~"), ".ssh", "cloudcontrol.unittest.id_dsa")
        self.__client_key_pub_filepath = "%s.pub" % self.__client_key_filepath

        self.__host_key_filepath = mkstemp(".cloudcontrol.unittest.hostkey")[1]
        self.__host_key_pub_filepath = "%s.pub" % self.__host_key_filepath
        
        # self.__authorized_keys_filepath = mkstemp(".cloudcontrol.unittest.authorized_keys2")[1]
        self.__authorized_keys_filepath = os.path.join(os.path.expanduser("~"), ".ssh", "cloudcontrol.unittest.authorized_keys2")
        
        # track all these files we are managing for easy removal
        self.__filepaths = [
            self.__authorized_keys_filepath,
            self.__client_key_filepath,
            self.__client_key_pub_filepath,
            self.__host_key_filepath,
            self.__host_key_pub_filepath,
        ]
        
        # get the binpaths
        self.__sshd_bin = Popen(["which", "sshd"], stdout=PIPE).communicate()[0].strip()
        self.__sshkeygen_bin = Popen(["which", "ssh-keygen"], stdout=PIPE).communicate()[0].strip()
        
        # make a reference to the pipe we'll use for the sshd process
        self.__sshd_pipe = None
        
        # patch the remote library to use our special keys for auth
        cloudcontrol._connections.remote._secret_unittest_ssh_key_filename = self.__client_key_filepath
    
    def __del__(self):
        # un-patch the remote library so that it uses normal keys again for auth
        cloudcontrol._connections.remote._secret_unittest_ssh_key_filename = None
        self.stop()
        
    def _clear_all_files(self):
        """clears all of the temporary files that we are managing"""
        for filepath in self.__filepaths:
            if os.path.exists(filepath):
                os.remove(filepath)
    
    def _ensure_cache_has_keys(self):
        """generates all keys and saves to the cache dir"""
        
        # make the cache path if it doesnt exist
        if not os.path.exists(_KEYCACHE_DIR):
            os.mkdir(_KEYCACHE_DIR)
        
        # create the keys if they don't exist
        if not os.path.exists(_CACHED_CLIENTKEY):
            if os.path.exists(_CACHED_CLIENTKEY_PUB):
                os.remove(_CACHED_CLIENTKEY_PUB)
            if 0 != Popen([self.__sshkeygen_bin, "-t", "dsa", "-f", _CACHED_CLIENTKEY, "-N", '']).wait():
                raise StandardError("unable to generate temporary DSA client key")

        if not os.path.exists(_CACHED_HOSTKEY):
            if os.path.exists(_CACHED_HOSTKEY_PUB):
                os.remove(_CACHED_HOSTKEY_PUB)
            if 0 != Popen([self.__sshkeygen_bin, "-t", "dsa", "-f", _CACHED_HOSTKEY, "-N", '']).wait():
                raise StandardError("unable to generate temporary DSA client key")
    
    def _populate_all_files(self):
        """copies keys into the proper files so that the sshd loopback will run"""

        # clear all old files and ensure the cache is populated
        self._clear_all_files()
        self._ensure_cache_has_keys()
        
        # copy all key files
        shutil.copy(_CACHED_HOSTKEY, self.__host_key_filepath)
        shutil.copy(_CACHED_HOSTKEY_PUB, self.__host_key_pub_filepath)
        shutil.copy(_CACHED_CLIENTKEY, self.__client_key_filepath)
        shutil.copy(_CACHED_CLIENTKEY_PUB, self.__client_key_pub_filepath)
        shutil.copy(_CACHED_CLIENTKEY_PUB, self.__authorized_keys_filepath)
    
    is_running = property(lambda self: self.__sshd_pipe is not None and self.__sshd_pipe.returncode is None)
    
    def start(self):
        """ensures that our sshd loopback is running"""
        self._populate_all_files()
        args = [self.__sshd_bin, "-D", "-h", self.__host_key_filepath, "-p", str(self.__port), "-oAuthorizedKeysFile=%s" % self.__authorized_keys_filepath]
        self.__sshd_pipe = Popen(args)
        
    def stop(self):
        """ensures that our sshd loopback is stopped"""
        if self.is_running:
            os.kill(self.__sshd_pipe.pid, SIGINT)
            self.__sshd_pipe = None
            self._clear_all_files()
